﻿/* Author: Achim Schnell */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace AppFx.Tokenization
{
    /// <summary>
    /// Creates tokens out of an expression.
    /// </summary>
    public class Tokenizer : ITokenizer, IList<IVerifier>
    {
        #region Fields

        private List<IToken> _CurrentTokens;

        /// <summary>
        /// The current, remaining buffer.
        /// </summary>
        private string buffer;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the given input.
        /// </summary>
        /// <value>
        /// The input.
        /// </value>
        public string Input { get; protected set; }

        /// <summary>
        /// A list of all registered verifiers.
        /// </summary>
        /// <value>
        /// All registered verifiers.
        /// </value>
        protected List<IVerifier> Verifiers { get; set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="Tokenizer"/> class.
        /// </summary>
        public Tokenizer() 
        {
            Verifiers = new List<IVerifier>();
        }

        #endregion

        #region Methods

        #region PublicMethods

        /// <summary>
        /// Parses the specified expression into tokens..
        /// </summary>
        /// <param name="expression">The expression to parse.</param>
        /// <returns>A list of extracted tokens.</returns>
        public IList<IToken> Parse(string expression)
        {
            // avoid NullReferenceException
            if (String.IsNullOrWhiteSpace(expression))
                throw new ArgumentException("Input empty.");

            // save given input
            Input = expression;

            // save given input into buffer for further processing
            buffer = expression;

            _CurrentTokens = new List<IToken>();

            // as long as there is input -> create tokens
            while (!IsInputEmpty())
            {
                var token = GetNextToken();
                // save the token
                _CurrentTokens.Add(token);
            }

            // return all parsed tokens.
            return _CurrentTokens;
        }

        #endregion

        #region ProtectedMethods

        /// <summary>
        /// Determines whether the input is empty.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if input is empty, otherwise, <c>false</c>.
        /// </returns>
        protected virtual bool IsInputEmpty()
        {
            if (buffer == null || buffer.Length == 0)
                return true;
            else
                return false;
        }

        protected virtual IToken GetNextToken()
        {
            // save a list of verifiers to exclude for this round
            List<IVerifier> excludeVerifiers = new List<IVerifier>();

            // loop through all chars and avoid too high indexes
            for (int index = 0; index < buffer.Length; index++)
            {
                // loop through each verifier and check if it accepts the given input
                foreach (var verifier in Verifiers.Except(excludeVerifiers))
                {
                    // check if the input matches the verifiers pattern
                    if (verifier.Verify(buffer[index]))
                    {
                        // save char to the verifiers buffer
                        verifier.Consume(buffer[index]);
                    }
                    else
                    {
                        // reset the verifier for the next token
                        verifier.Reset();
                        // exclude verifier for the next char
                        excludeVerifiers.Add(verifier);
                    }
                }
                // we got one verifier left - let it be all right and let it produce it's token.
                if (Verifiers.Except(excludeVerifiers).Count() == 1)
                {
                    return FinalizeToken(Verifiers.Except(excludeVerifiers).First(), index + 1);
                }
                // there's less than 1 verifier resulting in an unknown token
                else if (Verifiers.Except(excludeVerifiers).Count() < 1)
                {
                    var token = new Token(new string(new char[] { buffer[index] }), eTokenType.Unknown);
                    buffer = buffer.Substring(index+1);
                    return token;
                }
            }
            throw new Exception();
        }

        protected virtual IToken FinalizeToken(IVerifier verifier, int currentIndex)
        {
            int index = currentIndex;

            if (index <= (buffer.Length - 1))
            {
                while (verifier.Verify(buffer[index]))
                {
                    verifier.Consume(buffer[index]);
                    index = index + 1;
                    if (index > (buffer.Length - 1))
                        break;
                }
            }
            buffer = buffer.Substring(index);
            return verifier.Finalize();
        }

        #endregion

        #endregion

        #region IList<IVerifier>

        /// <summary>
        /// Bestimmt den Index eines bestimmten Elements in der <see cref="T:System.Collections.Generic.IList`1"/>.
        /// </summary>
        /// <param name="item">Das im <see cref="T:System.Collections.Generic.IList`1"/> zu suchende Objekt.</param>
        /// <returns>
        /// Der Index von <paramref name="item"/>, wenn das Element in der Liste gefunden wird, andernfalls -1.
        /// </returns>
        public int IndexOf(IVerifier item)
        {
            return Verifiers.IndexOf(item);
        }

        /// <summary>
        /// Fügt am angegebenen Index ein Element in die <see cref="T:System.Collections.Generic.IList`1"/> ein.
        /// </summary>
        /// <param name="index">Der nullbasierte Index, an dem <paramref name="item"/> eingefügt werden soll.</param>
        /// <param name="item">Das in die <see cref="T:System.Collections.Generic.IList`1"/> einzufügende Objekt.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> ist kein gültiger Index in der <see cref="T:System.Collections.Generic.IList`1"/>.</exception>
        ///   
        /// <exception cref="T:System.NotSupportedException">Die <see cref="T:System.Collections.Generic.IList`1"/> ist schreibgeschützt.</exception>
        public void Insert(int index, IVerifier item)
        {
            Verifiers.Insert(index, item);
        }

        /// <summary>
        /// Entfernt das <see cref="T:System.Collections.Generic.IList`1"/>-Element am angegebenen Index.
        /// </summary>
        /// <param name="index">Der nullbasierte Index des zu entfernenden Elements.</param>
        /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> ist kein gültiger Index in der <see cref="T:System.Collections.Generic.IList`1"/>.</exception>
        ///   
        /// <exception cref="T:System.NotSupportedException">Die <see cref="T:System.Collections.Generic.IList`1"/> ist schreibgeschützt.</exception>
        public void RemoveAt(int index)
        {
            Verifiers.RemoveAt(index);
        }

        /// <summary>
        /// Ruft das Element am angegebenen Index ab oder legt dieses fest.
        /// </summary>
        /// <returns>Das Element am angegebenen Index.</returns>
        ///   
        /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> ist kein gültiger Index in der <see cref="T:System.Collections.Generic.IList`1"/>.</exception>
        ///   
        /// <exception cref="T:System.NotSupportedException">Die Eigenschaft wird festgelegt, und die <see cref="T:System.Collections.Generic.IList`1"/> ist schreibgeschützt.</exception>
        public IVerifier this[int index]
        {
            get
            {
                return Verifiers[index];
            }
            set
            {
                Verifiers[index] = value;
            }
        }

        /// <summary>
        /// Fügt der <see cref="T:System.Collections.Generic.ICollection`1"/> ein Element hinzu.
        /// </summary>
        /// <param name="item">Das Objekt, das <see cref="T:System.Collections.Generic.ICollection`1"/> hinzugefügt werden soll.</param>
        /// <exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> ist schreibgeschützt.</exception>
        public void Add(IVerifier item)
        {
            Verifiers.Add(item);
        }

        /// <summary>
        /// Entfernt alle Elemente aus <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> ist schreibgeschützt. </exception>
        public void Clear()
        {
            Verifiers.Clear();
        }

        /// <summary>
        /// Bestimmt, ob <see cref="T:System.Collections.Generic.ICollection`1"/> einen bestimmten Wert enthält.
        /// </summary>
        /// <param name="item">Das im <see cref="T:System.Collections.Generic.ICollection`1"/> zu suchende Objekt.</param>
        /// <returns>
        /// true, wenn sich <paramref name="item"/> in <see cref="T:System.Collections.Generic.ICollection`1"/> befindet, andernfalls false.
        /// </returns>
        public bool Contains(IVerifier item)
        {
           return  Verifiers.Contains(item);
        }

        /// <summary>
        /// Copies to.
        /// </summary>
        /// <param name="array">The array.</param>
        /// <param name="arrayIndex">Index of the array.</param>
        public void CopyTo(IVerifier[] array, int arrayIndex)
        {
            Verifiers.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Ruft die Anzahl der Elemente ab, die in <see cref="T:System.Collections.Generic.ICollection`1"/> enthalten sind.
        /// </summary>
        /// <returns>Die Anzahl der Elemente, die in <see cref="T:System.Collections.Generic.ICollection`1"/> enthalten sind.</returns>
        public int Count
        {
            get { return Verifiers.Count; }
        }

        /// <summary>
        /// Ruft einen Wert ab, der angibt, ob <see cref="T:System.Collections.Generic.ICollection`1"/> schreibgeschützt ist.
        /// </summary>
        /// <returns>true, wenn <see cref="T:System.Collections.Generic.ICollection`1"/> schreibgeschützt ist, andernfalls false.</returns>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Entfernt das erste Vorkommen eines bestimmten Objekts aus <see cref="T:System.Collections.Generic.ICollection`1"/>.
        /// </summary>
        /// <param name="item">Das aus dem <see cref="T:System.Collections.Generic.ICollection`1"/> zu entfernende Objekt.</param>
        /// <returns>
        /// true, wenn <paramref name="item"/> erfolgreich aus <see cref="T:System.Collections.Generic.ICollection`1"/> gelöscht wurde, andernfalls false. Diese Methode gibt auch dann false zurück, wenn <paramref name="item"/> nicht in der ursprünglichen <see cref="T:System.Collections.Generic.ICollection`1"/> gefunden wurde.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException"><see cref="T:System.Collections.Generic.ICollection`1"/> ist schreibgeschützt.</exception>
        public bool Remove(IVerifier item)
        {
            return Verifiers.Remove(item);
        }

        /// <summary>
        /// Gibt einen Enumerator zurück, der die Auflistung durchläuft.
        /// </summary>
        /// <returns>
        /// Ein <see cref="T:System.Collections.Generic.IEnumerator`1"/>, der zum Durchlaufen der Auflistung verwendet werden kann.
        /// </returns>
        public IEnumerator<IVerifier> GetEnumerator()
        {
            return Verifiers.GetEnumerator();
        }

        /// <summary>
        /// Gibt einen Enumerator zurück, der eine Auflistung durchläuft.
        /// </summary>
        /// <returns>
        /// Ein <see cref="T:System.Collections.IEnumerator"/>-Objekt, das zum Durchlaufen der Auflistung verwendet werden kann.
        /// </returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Verifiers.GetEnumerator();
        }

        #endregion

        #region ITokenizer

        /// <summary>
        /// Gets the unprocessed part of the tokenizers buffer.
        /// </summary>
        /// <returns>
        /// The buffered expression.
        /// </returns>
        public string GetBuffer()
        {
            return buffer;
        }

        /// <summary>
        /// Get's a list of all currently parsed tokens.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<IToken> GetCurrentTokens()
        {
            return _CurrentTokens;
        }

        /// <summary>
        /// Parses the expression.
        /// </summary>
        /// <param name="expression">Expression to parse.</param>
        /// <returns>
        /// A list of parsed tokens.
        /// </returns>
        IEnumerable<IToken> ITokenizer.Tokenize(string expression)
        {
            return Parse(expression);
        }

        #endregion
    }
}
